<template>
  <transition-group
    class="h-full w-full"
    v-bind="$attrs"
    ref="elRef"
    :name="transitionName"
    :tag="tag"
    mode="out-in"
  >
    <div key="component" v-if="isInit">
      <slot :loading="loading"></slot>
    </div>
    <div key="skeleton" v-else>
      <slot name="skeleton" v-if="$slots.skeleton"></slot>
      <Skeleton v-else />
    </div>
  </transition-group>
</template>
<script lang="ts">
  import type { PropType } from 'vue'
  import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'
  import { Skeleton } from 'ant-design-vue'
  import { useTimeoutFn } from '/@/hooks/core/useTimeout'
  import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'

  interface State {
    isInit: boolean
    loading: boolean
    intersectionObserverInstance: IntersectionObserver | null
  }

  const props = {
    /**
     * Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
     */
    timeout: { type: Number },
    /**
     * The viewport where the component is located.
     * If the component is scrolling in the page container, the viewport is the container
     */
    viewport: {
      type: (typeof window !== 'undefined' ? window.HTMLElement : Object) as PropType<HTMLElement>,
      default: () => null,
    },
    /**
     * Preload threshold, css unit
     */
    threshold: { type: String, default: '0px' },
    /**
     * The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
     */
    direction: {
      type: String,
      default: 'vertical',
      validator: (v) => ['vertical', 'horizontal'].includes(v),
    },
    /**
     * The label name of the outer container that wraps the component
     */
    tag: { type: String, default: 'div' },
    maxWaitingTime: { type: Number, default: 80 },
    /**
     * transition name
     */
    transitionName: { type: String, default: 'lazy-container' },
  }

  export default defineComponent({
    name: 'LazyContainer',
    components: { Skeleton },
    inheritAttrs: false,
    props,
    emits: ['init'],
    setup(props, { emit }) {
      const elRef = ref()
      const state = reactive<State>({
        isInit: false,
        loading: false,
        intersectionObserverInstance: null,
      })

      onMounted(() => {
        immediateInit()
        initIntersectionObserver()
      })

      // If there is a set delay time, it will be executed immediately
      function immediateInit() {
        const { timeout } = props
        timeout &&
          useTimeoutFn(() => {
            init()
          }, timeout)
      }

      function init() {
        state.loading = true

        useTimeoutFn(() => {
          if (state.isInit) return
          state.isInit = true
          emit('init')
        }, props.maxWaitingTime || 80)
      }

      function initIntersectionObserver() {
        const { timeout, direction, threshold } = props
        if (timeout) return
        // According to the scrolling direction to construct the viewport margin, used to load in advance
        let rootMargin = '0px'
        switch (direction) {
          case 'vertical':
            rootMargin = `${threshold} 0px`
            break
          case 'horizontal':
            rootMargin = `0px ${threshold}`
            break
        }

        try {
          const { stop, observer } = useIntersectionObserver({
            rootMargin,
            target: toRef(elRef.value, '$el'),
            onIntersect: (entries: any[]) => {
              const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio
              if (isIntersecting) {
                init()
                if (observer) {
                  stop()
                }
              }
            },
            root: toRef(props, 'viewport'),
          })
        } catch (e) {
          init()
        }
      }
      return {
        elRef,
        ...toRefs(state),
      }
    },
  })
</script>
